/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package czStvDpds.graphics3D;

import czStvDpds.graphics3D.geometry.Texture;
import czStvDpds.graphics3D.geometry.TextureProperties;
import czStvDpds.logging.Logging;
import java.awt.image.*;
import java.nio.ByteBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.*;
import org.lwjgl.util.glu.GLU;

/**
 *
 * @author divad
 */
public final class TextureCreator {

    /**
     * Vygeneruje texturu v GL. Měla by být volána jen hlavní vykreslovací
     * jednotkou.
     *
     * @param tex
     * @see Texture
     */
    public static void generateTexture(Texture tex) {
        if (tex != null) {
            TextureProperties properties = tex.getProperties();
            if (properties == null) {
                properties = new TextureProperties();
            }
            ByteBuffer data = tex.getData();
            int id = GL11.glGenTextures();
            GL11.glBindTexture(properties.getType(), id);
            if (properties.getType() == TextureProperties.TEXTURE_1D) {
                GL11.glTexImage1D(GL11.GL_TEXTURE_2D, 0, properties.getFormat(), properties.getWidth(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
            } else if (properties.getType() == TextureProperties.TEXTURE_2D) {
                if (GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {
                    GL11.glTexParameteri(properties.getType(), GL14.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
                    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, properties.getFormat(), properties.getWidth(), properties.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                } else {
                    GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, properties.getFormat(), properties.getWidth(), properties.getHeight(), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                }
            } else if (properties.getType() == TextureProperties.TEXTURE_3D) {
                GL12.glTexImage3D(GL11.GL_TEXTURE_2D, 0, properties.getFormat(), properties.getWidth(), properties.getHeight(), properties.getDepth(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
            } else if (properties.getType() == TextureProperties.TEXTURE_CUBE_MAP) {
                if (GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {
                    GL11.glTexParameteri(properties.getType(), GL14.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
                    for (int i = 0; i < 6; i++) {
                        data.position((data.capacity() / 6) * i);
                        GL11.glTexImage2D(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, properties.getFormat(), properties.getWidth(), properties.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                    }
                    data.rewind();
                } else {
                    for (int i = 0; i < 6; i++) {
                        data.position((data.capacity() / 6) * i);
                        GLU.gluBuild2DMipmaps(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, properties.getFormat(), properties.getWidth(), properties.getHeight(), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                    }
                    data.rewind();
                }
            }
            tex.set(data, properties, id);
            tex.setInGL();
        } else {
            Logging.getMainLogger().info("Texture is null");
        }
    }

    /**
     * Převede rastrový obrázek na pole byte.
     *
     * @param rgbaImg
     * @return
     */
    public static ByteBuffer getImageData(BufferedImage rgbaImg) {
        if (rgbaImg != null) {
            ByteBuffer data = BufferUtils.createByteBuffer(rgbaImg.getWidth() * rgbaImg.getHeight() * 4);
            for (int y = 0; y < rgbaImg.getHeight(); y++) {
                for (int x = 0; x < rgbaImg.getWidth(); x++) {
                    int pixColor = rgbaImg.getRGB(x, y);
                    data.put((byte) ((pixColor >> 16) & 255));
                    data.put((byte) ((pixColor >> 8) & 255));
                    data.put((byte) ((pixColor >> 0) & 255));
                    data.put((byte) ((pixColor >> 24) & 255));
                }
            }
            data.rewind();
            return data;
        }
        return null;
    }

    /**
     * Převede pole obrázků na pole byte. Rozměry všech obrázků musí být stejné.
     * Použití při vytváření 3D textur a cube map. U 3D textur jsou v poli
     * obrázky seřazeny od nejbližšího (koordinát r = 0) až po nejvzdálenější
     * (koordinát r = 1). Počet obrázků musí být mocnia 2. U cube map jsou
     * obrázky seřazeny následovně - pravý, levý, horní, dolní, zadní, přední
     * (x,-x,y,-y,z,-z).
     *
     * @param rgbaImgs
     * @return
     */
    public static ByteBuffer getMultiImageData(BufferedImage[] rgbaImgs) {
        if (rgbaImgs != null && rgbaImgs.length > 0) {
            int width = rgbaImgs[0].getWidth();
            int height = rgbaImgs[0].getWidth();
            ByteBuffer data = BufferUtils.createByteBuffer(rgbaImgs[0].getWidth() * rgbaImgs[0].getHeight() * rgbaImgs.length * 4);
            for (int depth = 0; depth < rgbaImgs.length; depth++) {
                if (rgbaImgs[depth].getWidth() == width && rgbaImgs[depth].getHeight() == height) {
                    for (int y = 0; y < rgbaImgs[depth].getHeight(); y++) {
                        for (int x = 0; x < rgbaImgs[depth].getWidth(); x++) {
                            int pixColor = rgbaImgs[depth].getRGB(x, y);
                            data.put((byte) ((pixColor >> 16) & 255));
                            data.put((byte) ((pixColor >> 8) & 255));
                            data.put((byte) ((pixColor >> 0) & 255));
                            data.put((byte) ((pixColor >> 24) & 255));
                        }
                    }
                } else {
                    return null;
                }
            }
            data.rewind();
            return data;
        }
        return null;
    }

    /**
     * Změní vybraná data textury. Měla by být volána jen hlavní vykreslovací
     * jednotkou.
     *
     * @param tex - textura ke změnění
     * @param x - x souřadnice levého horního předního rohu změné části textury.
     * @param y - y souřadnice levého horního předního rohu změné části textury.
     * (u 1D textur nastavíme 0)
     * @param z - z souřadnice levého horního předního rohu změné části textury.
     * (U cube mapy z je číslo strany - postipně od nuly - pravá, levá, horní,
     * dolní, přední ,zadní; U 1D a 2D textur nastavíme 0)
     * @param width - šířka změněné části textury
     * @param height - výška změněné části textury
     * @param depth - hloubka změněné části textury
     * @param data - data ke změnění
     */
    public static void changeTexture(Texture tex, int x, int y, int z, int width, int height, int depth, ByteBuffer data) {
        if (tex != null && data != null) {
            if (x + width > 0 && x + width < tex.getProperties().getWidth()
                    && y + height > 0 && y + height < tex.getProperties().getHeight()
                    && z + depth > 0 && z + depth < tex.getProperties().getDepth()) {
                TextureCreator.changeData(tex.getProperties().getWidth(), tex.getProperties().getHeight(), tex.getProperties().getDepth(), x, y, z, width, height, depth, tex.getData(), data);
                GL11.glBindTexture(tex.getProperties().getType(), tex.getTextureID());
                if (tex.getProperties().getType() == TextureProperties.TEXTURE_1D) {
                    GL11.glTexSubImage1D(GL11.GL_TEXTURE_1D, 0, x, width, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                } else if (tex.getProperties().getType() == TextureProperties.TEXTURE_2D) {
                    if (GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {
                        GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, x, y, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                    } else {
                        GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, tex.getProperties().getFormat(), tex.getProperties().getWidth(), tex.getProperties().getHeight(), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                    }
                } else if (tex.getProperties().getType() == TextureProperties.TEXTURE_3D) {
                    GL12.glTexSubImage3D(GL12.GL_TEXTURE_3D, 0, x, y, z, width, height, depth, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                } else if (tex.getProperties().getType() == TextureProperties.TEXTURE_CUBE_MAP) {
                    for (int d = 0; d < depth; d++) {
                        if (GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) {
                            int dataPosition = data.position();
                            data.position((data.remaining() / 6) * (z + d));
                            GL11.glTexSubImage2D(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (z + d), 0, x, y, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                            data.position(dataPosition);
                        } else {
                            int dataPosition = data.position();
                            data.position((data.remaining() / 6) * (z + d));
                            GLU.gluBuild2DMipmaps(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (z + d), tex.getProperties().getFormat(), tex.getProperties().getWidth(), tex.getProperties().getHeight(), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
                            data.position(dataPosition);
                        }
                    }
                }
            }
        }
    }

    private static ByteBuffer changeData(int width, int height, int depth, int x, int y, int z, int partWidth, int partHeight, int partDepth, ByteBuffer originalData, ByteBuffer change) {
        int changePosition = change.position();
        for (int d = 0; d < partDepth; d++) {
            for (int h = 0; h < partHeight; h++) {
                for (int w = 0; w < partWidth; w++) {
                    int realX = x + w;
                    int realY = y + h;
                    int realZ = z + d;
                    originalData.position((realZ * width * height + realY * width + realX) * 4);
                    originalData.put(change.get());
                    originalData.put(change.get());
                    originalData.put(change.get());
                    originalData.put(change.get());
                }
            }

        }
        return originalData;
    }
}
